001    /*
002     * Copyright 2005 Stephen J. McConnell
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.library.impl;
020    
021    import java.io.File;
022    import java.io.IOException;
023    import java.net.URI;
024    import java.net.URISyntaxException;
025    import java.text.SimpleDateFormat;
026    import java.util.Arrays;
027    import java.util.ArrayList;
028    import java.util.List;
029    import java.util.Date;
030    import java.util.TimeZone;
031    import java.util.Properties;
032    import java.util.Map;
033    import java.util.Hashtable;
034    
035    import net.dpml.lang.Category;
036    import net.dpml.lang.Version;
037    
038    import net.dpml.library.Info;
039    import net.dpml.library.Filter;
040    import net.dpml.library.Library;
041    import net.dpml.library.Module;
042    import net.dpml.library.Resource;
043    import net.dpml.library.Type;
044    import net.dpml.library.Data;
045    import net.dpml.library.ResourceNotFoundException;
046    import net.dpml.library.info.InfoDirective;
047    import net.dpml.library.info.TypeDirective;
048    import net.dpml.library.info.ResourceDirective;
049    import net.dpml.library.info.ResourceDirective.Classifier;
050    import net.dpml.library.info.IncludeDirective;
051    import net.dpml.library.info.DependencyDirective;
052    import net.dpml.library.info.AbstractDirective;
053    import net.dpml.library.info.ValidationException;
054    import net.dpml.library.info.FilterDirective;
055    import net.dpml.library.info.Scope;
056    
057    import net.dpml.transit.Artifact;
058    import net.dpml.transit.Transit;
059    
060    import net.dpml.util.Resolver;
061    
062    
063    /**
064     * Implementation of a resource.
065     *
066     * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
067     * @version 1.0.0
068     */
069    public class DefaultResource extends DefaultDictionary implements Resource, Resolver, Comparable
070    {
071       /**
072        * Timestamp.
073        */
074        public static final String TIMESTAMP = getTimestamp();
075        
076       /**
077        * Constant SNAPSHOT symbol.
078        */
079        public static final String SNAPSHOT = "SNAPSHOT";
080        
081       /**
082        * Constant RELEASE symbol.
083        */
084        public static final String RELEASE = "RELEASE";
085        
086        private final DefaultLibrary m_library;
087        private final ResourceDirective m_directive;
088        private final DefaultModule m_parent;
089        private final Type[] m_types;
090        private final String[] m_typeNames;
091        private final String m_path;
092        private final File m_basedir;
093        private final Map m_filters = new Hashtable();
094        private final Data[] m_data;
095        
096       /**
097        * Creation of a new default resource.
098        * @param logger the assigned logging channel
099        * @param library the reference library
100        * @param directive the directive
101        */
102        DefaultResource( final DefaultLibrary library, final AbstractDirective directive )
103        {
104            super( null, directive );
105            
106            m_library = library;
107            m_directive = null;
108            m_parent = null;
109            m_types = new Type[0];
110            m_typeNames = new String[0];
111            m_path = "";
112            m_basedir = null;
113            m_data = new Data[0];
114        }
115        
116       /**
117        * Creation of a new default resource.
118        * @param logger the assigned logging channel
119        * @param library the reference library
120        * @param module the parent module
121        * @param directive the resource directive
122        */
123        DefaultResource( 
124          final DefaultLibrary library, final DefaultModule module, final ResourceDirective directive ) 
125        {
126            super( module, directive );
127            if( null == directive )
128            {
129                throw new NullPointerException( "directive" );
130            }
131            
132            m_library = library;
133            m_directive = directive;
134            m_parent = module;
135            
136            if( module.isRoot() )
137            {
138                m_path = directive.getName();
139            }
140            else
141            {
142                m_path = module.getResourcePath() + "/" + directive.getName();
143            }
144            
145            // setup produced types
146            
147            //m_types = buildTypes( directive.getTypeDirectives() );
148            m_types = directive.getTypeDirectives();
149            m_typeNames = new String[ m_types.length ];
150            for( int i=0; i<m_types.length; i++ )
151            {
152                Type type = m_types[i];
153                m_typeNames[i] = type.getID();
154            }
155            
156            // setup production data
157            
158            m_data = new Data[0];
159            
160            // setup the resource basedir
161            
162            File anchor = getAnchor();
163            String filename = m_directive.getBasedir();
164            if( null != filename )
165            {
166                String spec = resolve( filename );
167                File file = new File( spec );
168                if( file.isAbsolute() )
169                {
170                    m_basedir = getCanonicalFile( file );
171                }
172                else
173                {
174                    File basedir = new File( anchor, spec );
175                    m_basedir = getCanonicalFile( basedir );
176                    setProperty( "basedir", m_basedir.toString() );
177                }
178            }
179            else
180            {
181                if( !m_directive.getClassifier().equals( Classifier.LOCAL ) )
182                {
183                    m_basedir = null;
184                }
185                else
186                {
187                    final String error = 
188                      "Missing base directory declaration in resource ["
189                      + m_path
190                      + "].";
191                    throw new ValidationException( error );
192                }
193            }
194            
195            // setup the default properties
196            
197            setProperty( "project.name", getName() );
198            if( null != m_parent )
199            {
200                setProperty( "project.group", m_parent.getResourcePath() );
201            }
202            else
203            {
204                setProperty( "project.group", "" );
205            }
206            String version = getVersion();
207            if( null != version )
208            {
209                setProperty( "project.version", getVersion() );
210            }
211            
212            FilterDirective[] filters = directive.getFilterDirectives();
213            for( int i=0; i<filters.length; i++ )
214            {
215                FilterDirective filter = filters[i];
216                String token = filter.getToken();
217                m_filters.put( token, filter );
218            }
219        }
220        
221        //----------------------------------------------------------------------------
222        // Resource
223        //----------------------------------------------------------------------------
224        
225       /**
226        * Return a data directives.
227        * @return the associated production data
228        */
229        public Data[] getData()
230        {
231            return m_data;
232        }
233    
234       /**
235        * Return the singleton library.
236        * @return the library
237        */
238        public Library getLibrary()
239        {
240            return m_library;
241        }
242    
243       /**
244        * Return the name of the resource.
245        * @return the resource name
246        */
247        public String getName()
248        {
249            if( null != m_directive )
250            {
251                return m_directive.getName();
252            }
253            else
254            {
255                return null;
256            }
257        }
258        
259       /**
260        * Return the resource version.
261        * @return the version
262        */
263        public String getVersion()
264        {
265            if( null == m_directive )
266            {
267                return getStandardVersion();
268            }
269            if( ResourceDirective.ANONYMOUS.equals( getClassifier() ) )
270            {
271                return m_directive.getVersion();
272            }
273            else
274            {
275                String version = getStatutoryVersion();
276                if( null != version )
277                {
278                    return version;
279                }
280                else
281                {
282                    return getStandardVersion();
283                }
284            }
285        }
286        
287       /**
288        * Return the declard resource version.
289        * @return the statutory version
290        */
291        public String getStatutoryVersion()
292        {
293            if( null == m_directive )
294            {
295                return null;
296            }
297            else
298            {
299                String version = m_directive.getVersion();
300                if( null != version )
301                {
302                    return version;
303                }
304                else
305                {
306                    if( null != m_parent )
307                    {
308                        return m_parent.getStatutoryVersion();
309                    }
310                    else
311                    {
312                        return null;
313                    }
314                }
315            }
316        }
317    
318       /**
319        * Return the decimal version.  If version prefixing is enabled
320        * via the <tt>project.version-prefix.enabled</tt> property then the value
321        * returned will be derived from the project major, minor and micro version
322        * properties, otherwise a null value will be returned.
323        *
324        * @return the version
325        */
326        public Version getDecimalVersion()
327        {
328            boolean flag = getBooleanProperty( "project.version-prefix.enabled", false );
329            if( flag )
330            {
331                int major = getMajorVersion();
332                int minor = getMinorVersion();
333                int micro = getMicroVersion();
334                return new Version( major, minor, micro );
335            }
336            else
337            {
338                return null;
339            }
340        }
341        
342       /**
343        * Return the fully qualified path to the resource.
344        * @return the path
345        */
346        public String getResourcePath()
347        {
348            return m_path;
349        }
350        
351       /**
352        * Return the basedir for this resource.
353        * @return the base directory (possibly null)
354        */
355        public File getBaseDir()
356        {
357            return m_basedir;
358        }
359    
360       /**
361        * Return the resource classifier.
362        * @return the classifier (LOCAL, EXTERNAL or ANONYMOUS)
363        */
364        public Classifier getClassifier()
365        {
366            if( null != m_directive )
367            {
368                return m_directive.getClassifier();
369            }
370            else
371            {
372                return ResourceDirective.ANONYMOUS;
373            }
374        }
375        
376       /**
377        * Return the info block.
378        * @return the info block
379        */
380        public Info getInfo()
381        {
382            return m_directive.getInfoDirective();
383        }
384        
385       /**
386        * Return the expanded array of types associated with the resource.
387        * The returned array is a function of the types declared by a resource
388        * expanded relative to any types implied by processor dependencies.
389        * @return the type array
390        */
391        public Type[] getTypes()
392        {
393            return m_types;
394        }
395        
396       /**
397        * Test if this resource is associated with a type of the supplied name.
398        * @param type the type id
399        * @return TRUE if this resource produces an artifact of the supplied type
400        */
401        public boolean isa( final String type )
402        {
403            for( int i=0; i<m_types.length; i++ )
404            {
405                Type someType = m_types[i];
406                String name = someType.getID();
407                if( name.equals( type ) )
408                {
409                    return true;
410                }
411            }
412            return false;
413        }
414        
415       /**
416        * Return a resource type relative to a supplied type id.
417        * @param id the type name to retrieve
418        * @return the type instance
419        * @exception IllegalArgumentException if the id value does not match
420        * a type produced by the resource.
421        */
422        public Type getType( final String id ) throws IllegalArgumentException
423        {
424            for( int i=0; i<m_types.length; i++ )
425            {
426                Type type = m_types[i];
427                if( type.getID().equals( id ) )
428                {
429                    return type;
430                }
431            }
432            final String error = 
433              "Type name ["
434              + id
435              + "] not recognized with the scope of resource ["
436              + getResourcePath()
437              + "].";
438            throw new IllegalArgumentException( error );
439        }
440        
441       /**
442        * Construct an link artifact for the supplied type.
443        * @param id the resource type id
444        * @return the link artifact
445        */
446        public Artifact getLinkArtifact( final String id )
447        {
448            if( null == m_directive )
449            {
450                final String error = 
451                  "Method not supported on virtual root.";
452                throw new UnsupportedOperationException( error );
453            }
454            if( null == id )
455            {
456                throw new NullPointerException( "id" );
457            }
458            String group = getGroupName();
459            String name = getName();
460            Type type = getType( id );
461            Version version = type.getVersion();
462            if( null == version )
463            {
464                final String error = 
465                  "Resource does not declare production of an alias for the requested type."
466                  + "\nResource: " + this
467                  + "\nType: " + id;
468                throw new IllegalArgumentException( error );
469            }
470            try
471            {
472                String spec = "link:" + id;
473                if( null != group )
474                {
475                    spec = spec + ":" + group + "/" + name;
476                }
477                else
478                {
479                    spec = spec + ":" + name;
480                }
481                if( !Version.NULL_VERSION.equals( version ) )
482                {
483                    int major = version.getMajor();
484                    int minor = version.getMinor();
485                    spec = spec + "#"
486                      + major
487                      + "."
488                      + minor;
489                }
490                return Artifact.createArtifact( spec );
491            }
492            catch( Throwable e )
493            {
494                final String error = 
495                  "Failed to construct link artifact for resource ["
496                  + getResourcePath()
497                  + "].";
498                throw new RuntimeException( error, e );
499            }
500        }
501    
502       /**
503        * Construct an artifact for the supplied type.
504        * @param id the resource type identifier
505        * @return the artifact
506        */
507        public Artifact getArtifact( final String id )
508        {
509            if( null == m_directive )
510            {
511                final String error = 
512                  "Method not supported on virtual root.";
513                throw new UnsupportedOperationException( error );
514            }
515            if( null == id )
516            {
517                throw new NullPointerException( "id" );
518            }
519            
520            String group = getGroupName();
521            String name = getName();
522            String version = getVersion();
523            String scheme = m_directive.getScheme();
524            
525            try
526            {
527                return Artifact.createArtifact( scheme, group, name, version, id );
528            }
529            catch( Throwable e )
530            {
531                final String error = 
532                  "Failed to construct artifact for resource ["
533                  + getResourcePath()
534                  + "].";
535                throw new RuntimeException( error, e );
536            }
537        }
538        
539       /**
540        * Return the enclosing parent module.
541        * @return the enclosing module of null if this a top-level module.
542        */
543        public Module getParent()
544        {
545            return getDefaultParent();
546        }
547        
548       /**
549        * Return an array of filters associated with the resource.
550        * @return the array of filters
551        */
552        public Filter[] getFilters()
553        {
554            DefaultModule module = getDefaultParent();
555            if( null != module )
556            {
557                Map map = new Hashtable();
558                Filter[] filters = module.getFilters();
559                for( int i=0; i<filters.length; i++ )
560                {
561                    Filter filter = filters[i];
562                    String token = filter.getToken();
563                    map.put( token, filter );
564                }
565                Filter[] local = getLocalFilters();
566                for( int i=0; i<local.length; i++ )
567                {
568                    Filter filter = local[i];
569                    String token = filter.getToken();
570                    map.put( token, filter );
571                }
572                return (Filter[]) map.values().toArray( new Filter[0] );
573            }
574            else
575            {
576                return getLocalFilters();
577            }
578        }
579        
580        //----------------------------------------------------------------------------
581        // Resolver
582        //----------------------------------------------------------------------------
583    
584       /**
585        * Utility function supporting resolution of uris containing 'resource' or 
586        * 'alias' schemes.  If the supplied uri schem is 'resource' or 'alias' the 
587        * reference is resolved to a artifact type, group and name from which a 
588        * resource is resolved and the uri returned.  If the scheme is resource
589        * the usri of the resource is returned. If the scheme is 'alias' a 
590        * linkn alias is returned.  If the scheme is not 'resource' or 'alias' 
591        * the argument will be evaluated as a normal transit artifact uri 
592        * specification.
593        * 
594        * @param ref the uri argument
595        * @return the uri value
596        * @exception URISyntaxException if an error occurs during uri creation
597        */
598        public URI toURI( final String ref ) throws URISyntaxException
599        {
600            Artifact spec = Artifact.createArtifact( ref );
601            if( spec.isRecognized() )
602            {
603                return spec.toURI();
604            }
605            else if( ref.startsWith( "resource:" ) || ref.startsWith( "alias:" ) )
606            {
607                String type = spec.getType();
608                String group = spec.getGroup();
609                String name = spec.getName();
610                String path = group + "/" + name;
611                Library library = getLibrary();
612                try
613                {
614                    Resource resource = library.getResource( path );
615                    if( ref.startsWith( "resource:" ) )
616                    {
617                        Artifact artifact = resource.getArtifact( type );
618                        return artifact.toURI();
619                    }
620                    else
621                    {
622                        Artifact artifact = resource.getLinkArtifact( type );
623                        return artifact.toURI();
624                    }
625                }
626                catch( ResourceNotFoundException e )
627                {
628                    final String error = 
629                      "Unresolvable resource reference: " + path;
630                    IllegalArgumentException iae = new IllegalArgumentException( error );
631                    iae.initCause( e );
632                    throw iae;
633                }
634            }
635            else
636            {
637                return spec.toURI();
638            }
639        }
640        
641        //----------------------------------------------------------------------------
642        // implementation
643        //----------------------------------------------------------------------------
644    
645        Map getFilterMap()
646        {
647            return m_filters;
648        }
649        
650        Filter[] getLocalFilters()
651        {
652            return (Filter[]) getFilterMap().values().toArray( new Filter[0] );
653        }
654        
655       /**
656        * Return an array of resource that are providers to this resource.
657        * @param scope the operational scope
658        * @param expand if true include transitive dependencies
659        * @param sort if true the array will sorted relative to dependencies
660        * @return the resource providers
661        */
662        public Resource[] getProviders( final Scope scope, final boolean expand, final boolean sort )
663        {
664            return getDefaultProviders( scope, expand, sort );
665        }
666        
667       /**
668        * Return an array of resource that are providers to this resource. If
669        * the supplied scope is BUILD the returned resource array is equivalent
670        * <src>getProviders( Scope.BUILD, .. )</src>.  If the scope is RUNTIME
671        * the returned resource array includes BUILD and RUNTIME resources. If 
672        * the scope is TEST the returned array includes BUILD, RUNTIME and TEST
673        * resources.
674        * @param scope the scope of aggregation to be applied to the selection
675        * @param expand if TRUE include transitive dependencies
676        * @param sort if true the array will sorted relative to dependencies
677        * @return the resource providers
678        */
679        public Resource[] getAggregatedProviders( final Scope scope, final boolean expand, final boolean sort )
680        {
681            return getAggregatedDefaultProviders( scope, expand, sort, false );
682        }
683        
684       /**
685        * Return a sorted and filtered array of providers. Resources not declaring
686        * the "jar" type as a produced type are excluded from selection.  The 
687        * resource array will include transitive dependencies.  The method is 
688        * suitable for the construction of build and test phase classloaders.
689        *
690        * @param scope the aggregation scope
691        * @return the scoped resource chain
692        */
693        public Resource[] getClasspathProviders( final Scope scope )
694        {
695            DefaultResource[] result = getAggregatedDefaultProviders( scope, true, true, true );
696            List stack = new ArrayList();
697            for( int i=0; i<result.length; i++ )
698            {
699                DefaultResource resource = result[i];
700                if( resource.isa( "jar" ) )
701                {
702                    stack.add( resource );
703                }
704            }
705            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
706        }
707    
708       /**
709        * Return an array of runtime providers filtered relative to a supplied
710        * classloading category.  Resources not declaring the "jar" type as a 
711        * produced type are excluded from selection.  The resource array returned 
712        * from this operation is a sorted transitive sequence excluding all 
713        * resource references by any category higher than the supplied category.
714        * This method is typically used to construct information suitable for 
715        * the gerneration of plugin metadata.
716        *
717        * @param category the classloader category
718        * @return the category scoped resource chain
719        */
720        public Resource[] getClasspathProviders( final Category category )
721        {
722            DefaultResource[] resources = getClasspathDefaultProviders( category );
723            return sortDefaultResources( resources, Scope.RUNTIME );
724        }
725        
726       /**
727        * Return an array of resources that are consumers of this resource.
728        * @param expand if true the returned array includes consumers associated
729        *   through transitive dependency relationships, otherwise the array is 
730        *   limited to direct consumers
731        * @param sort if true the array is sorted relative to depenency relationships
732        * @return the array of consumer projects
733        */
734        public Resource[] getConsumers( final boolean expand, final boolean sort )
735        {
736            return getDefaultConsumers( expand, sort );
737        }
738        
739       /**
740        * Return the underlying resource defintion.
741        * @return the resource directive
742        */
743        public ResourceDirective getResourceDirective()
744        {
745            return m_directive;
746        }
747    
748       /**
749        * Return a filename using the layout strategy employed by the cache.
750        * @param id the artifact type
751        * @return the filename
752        */
753        public String getLayoutPath( final String id )
754        {
755            Artifact artifact = getArtifact( id );
756            return Transit.getInstance().getCacheLayout().resolveFilename( artifact );
757        }
758    
759       /**
760        * Return a directive suitable for publication as an external description.
761        * @param module the enclosing module
762        * @return the resource directive
763        */
764        ResourceDirective exportResource( final DefaultModule module )
765        {
766            if( null == m_directive )
767            {
768                final String error = 
769                  "Cannot export from the root module.";
770                throw new UnsupportedOperationException( error );
771            }
772            String name = getName();
773            String version = getVersion();
774            String basedir = null;
775            InfoDirective info = m_directive.getInfoDirective();
776            TypeDirective[] types = m_directive.getTypeDirectives();
777            TypeDirective[] exportedTypes = createExportedTypes( types );
778            DependencyDirective[] dependencies = createDeps( module );
779            Properties properties = getExportProperties();
780            return ResourceDirective.createResourceDirective( 
781              name, version, Classifier.EXTERNAL, basedir,
782              info, exportedTypes, dependencies, properties, null );
783        }
784        
785        TypeDirective[] createExportedTypes( final TypeDirective[] types )
786        {
787            TypeDirective[] export = new TypeDirective[ types.length ]; 
788            for( int i=0; i<export.length; i++ )
789            {
790                TypeDirective type = types[i];
791                String id = type.getID();
792                Version version = type.getVersion();
793                export[i] = new TypeDirective( id, version );
794            }
795            return export;
796        }
797        
798        private DependencyDirective[] createDeps( final DefaultModule module )
799        {
800            ArrayList list = new ArrayList();
801            createIncludeDirectives( module, list, Category.SYSTEM );
802            createIncludeDirectives( module, list, Category.PUBLIC );
803            createIncludeDirectives( module, list, Category.PROTECTED );
804            createIncludeDirectives( module, list, Category.PRIVATE );
805            if( list.size() == 0 )
806            {
807                return new DependencyDirective[0];
808            }
809            else
810            {
811                IncludeDirective[] includes = 
812                 (IncludeDirective[]) list.toArray( new IncludeDirective[0] );
813                DependencyDirective runtime = 
814                  new DependencyDirective( Scope.RUNTIME, includes );
815                return new DependencyDirective[]{runtime};
816            }
817        }
818        
819        boolean isaDescendant( final DefaultModule module )
820        {
821            if( module == this )
822            {
823                return true;
824            }
825            if( m_parent == null )
826            {
827                return false;
828            }
829            else
830            {
831                if( m_parent == module )
832                {
833                    return true;
834                }
835                else
836                {
837                    return m_parent.isaDescendant( module );
838                }
839            }
840        }
841    
842        private void createIncludeDirectives(
843          final DefaultModule module, final List list, final Category category )
844        {
845            DefaultResource[] providers = 
846              getDefaultProviders( Scope.RUNTIME, true, category );
847            for( int i=0; i<providers.length; i++ )
848            {
849                DefaultResource provider = providers[i];
850                if( provider.isaDescendant( module ) )
851                {
852                    // create a ref
853                    String path = provider.getResourcePath();
854                    IncludeDirective include = 
855                      new IncludeDirective( 
856                        IncludeDirective.REF,
857                        category,
858                        path,
859                        null );
860                    list.add( include );
861                }
862                else
863                {
864                    // create a urn
865                    
866                    Type[] types = provider.getTypes();
867                    for( int j=0; j<types.length; j++ )
868                    {
869                        Type type = types[j];
870                        String label = type.getID();
871                        Artifact artifact = provider.getArtifact( label );
872                        String urn = artifact.toString();
873                        IncludeDirective include = 
874                          new IncludeDirective( 
875                            IncludeDirective.URI,
876                            category,
877                            urn,
878                            null );
879                        list.add( include );
880                    }
881                }
882            }
883        }
884        
885        //----------------------------------------------------------------------------
886        // Object
887        //----------------------------------------------------------------------------
888        
889       /**
890        * Return a string representation of the resource in the form 'resource:[path]'.
891        * @return the string value
892        */
893        public String toString()
894        {
895            if( null != m_directive )
896            {
897                if( m_directive.isLocal() )
898                {
899                    return toString( "project" );
900                }
901            }
902            return toString( "resource" );
903        }
904        
905        String toString( final String type )
906        {
907            return type + ":" + getResourcePath() + "#" + getVersion();
908        }
909        
910       /**
911        * Compare this object with another.
912        * @param other the other object
913        * @return the comparitive index
914        */
915        public int compareTo( final Object other )
916        {
917            if( other instanceof DefaultResource )
918            {
919                DefaultResource resource = (DefaultResource) other;
920                return getResourcePath().compareTo( resource.m_path );
921            }
922            else
923            {
924                return -1;
925            }
926        }
927        
928        //----------------------------------------------------------------------------
929        // internals
930        //----------------------------------------------------------------------------
931        
932       /**
933        * Return the singlton library.
934        * @return the library
935        */
936        DefaultLibrary getDefaultLibrary()
937        {
938            return m_library;
939        }
940        
941        boolean isAnonymous()
942        {
943            if( null != m_directive )
944            {
945                return m_directive.isAnonymous();
946            }
947            return false;
948        }
949        
950        boolean isLocal()
951        {
952            if( null != m_directive )
953            {
954                return m_directive.isLocal();
955            }
956            return false;
957        }
958        
959        DefaultModule getDefaultParent()
960        {
961            if( null != m_parent )
962            {
963                if( m_parent.isRoot() )
964                {
965                    return null;
966                }
967            }
968            return m_parent;
969        }
970        
971        DefaultResource[] getAggregatedDefaultProviders( 
972          final Scope scope, final boolean expanded, final boolean sort, final boolean flag )
973        {
974            DefaultResource[] resources = 
975              getAggregatedDefaultProviders( scope, expanded, flag );
976            if( sort )
977            {
978                return sortDefaultResources( resources, scope );
979            }
980            else
981            {
982                Arrays.sort( resources );
983                return resources;
984            }
985        }
986        
987        DefaultResource[] getAggregatedDefaultProviders( 
988          final Scope scope, final boolean expanded, final boolean flag )
989        {
990            ArrayList list = new ArrayList();
991            if( !flag )
992            {
993                aggregateProviders( list, Scope.BUILD );
994            }
995            if( scope.isGreaterThan( Scope.BUILD ) )
996            {
997                aggregateProviders( list, Scope.RUNTIME );
998            }
999            if( scope.isGreaterThan( Scope.RUNTIME ) )
1000            {
1001                aggregateProviders( list, Scope.TEST );
1002            }
1003            DefaultResource[] result = (DefaultResource[]) list.toArray( new DefaultResource[0] );
1004            if( expanded )
1005            {
1006                List visited = new ArrayList();
1007                List stack = new ArrayList();
1008                for( int i=0; i<result.length; i++ )
1009                {
1010                    DefaultResource resource = result[i];
1011                    expandDefaultResource( visited, stack, scope, resource );
1012                }
1013                result = (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1014            }
1015            return result;
1016        }
1017    
1018        private void aggregateProviders( final List list, final Scope scope )
1019        {
1020            DefaultResource[] resources = getDefaultProviders( scope, false, null );
1021            for( int i=0; i<resources.length; i++ )
1022            {
1023                DefaultResource resource = resources[i];
1024                if( !list.contains( resource ) )
1025                {
1026                    list.add( resource );
1027                }
1028            }
1029        }
1030        
1031        DefaultResource[] getDefaultProviders( 
1032          final Scope scope, final boolean expanded, final boolean sort ) 
1033        {
1034            DefaultResource[] resources = getDefaultProviders( scope, expanded, null );
1035            if( sort )
1036            {
1037                return sortDefaultResources( resources, scope );
1038            }
1039            else
1040            {
1041                Arrays.sort( resources );
1042                return resources;
1043            }
1044        }
1045        
1046        DefaultResource[] getDefaultProviders( 
1047          final Scope scope, final boolean expand, final Category category )
1048        {
1049            ArrayList visited = new ArrayList();
1050            ArrayList stack = new ArrayList();
1051            DefaultResource[] providers = getLocalDefaultProviders( scope, category );
1052            for( int i=0; i<providers.length; i++ )
1053            {
1054                DefaultResource provider = providers[i];
1055                if( expand )
1056                {
1057                    expandDefaultResource( visited, stack, scope, provider );
1058                }
1059                else if( !stack.contains( provider ) )
1060                {
1061                    stack.add( provider );
1062                }
1063            }
1064            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1065        }
1066        
1067        DefaultResource[] getLocalDefaultProviders( final Scope scope, final Category category ) 
1068        {
1069            if( null == m_directive )
1070            {
1071                return new DefaultResource[0];
1072            }
1073            IncludeDirective[] includes = getLocalIncludes( scope, category );
1074            DefaultResource[] resources = new DefaultResource[ includes.length ];
1075            for( int i=0; i<includes.length; i++ )
1076            {
1077                IncludeDirective include = includes[i];
1078                if( include.getMode().equals( IncludeDirective.URI ) )
1079                {
1080                    try
1081                    {
1082                        String value = include.getValue();
1083                        String urn = resolve( value );
1084                        Properties properties = include.getProperties();
1085                        resources[i] = m_library.getAnonymousResource( urn, properties );
1086                    }
1087                    catch( URISyntaxException e )
1088                    {
1089                        final String error = 
1090                          "Invalid uri value: " + include.getValue();
1091                        throw new RuntimeException( error, e );
1092                    }
1093                    catch( InvalidNameException e )
1094                    {
1095                        final String error = 
1096                          "An anonomous dependency include reference to ["
1097                          + include
1098                          + "] within the resource ["
1099                          + getResourcePath()
1100                          + "] could not be resolved.";
1101                        throw new InvalidNameException( error, e );
1102                    }
1103                    catch( Exception e )
1104                    {
1105                        final String error = 
1106                          "Unexpected error during dynamic resource creation.";
1107                        throw new RuntimeException( error, e );
1108                    }
1109                }
1110                else
1111                {
1112                    String ref = getIncludeReference( include );
1113                    try
1114                    {
1115                        DefaultResource resource = m_library.getDefaultResource( ref );
1116                        resources[i] = resource;
1117                    }
1118                    catch( InvalidNameException e )
1119                    {
1120                        if( null == category )
1121                        {
1122                            final String error = 
1123                              "A dependency include ["
1124                              + ref
1125                              + "] within ["
1126                              + this
1127                              + "] referencing ["
1128                              + ref
1129                              + "] under the scope ["
1130                              + scope
1131                              + "] is unknown.";
1132                            throw new InvalidNameException( error );
1133                        }
1134                        else
1135                        {
1136                            final String error = 
1137                              "A dependency include within ["
1138                              + this
1139                              + "] referencing ["
1140                              + ref
1141                              + "] under the scope ["
1142                              + scope
1143                              + "] and category ["
1144                              + category
1145                              + "] is unknown.";
1146                            throw new InvalidNameException( error );
1147                        }
1148                    }
1149                }
1150            }
1151            return resources;
1152        }
1153        
1154        private IncludeDirective[] getLocalIncludes( final Scope scope, final Category category )
1155        {
1156            DependencyDirective dependency = m_directive.getDependencyDirective( scope );
1157            if( null == category )
1158            {
1159                return dependency.getIncludeDirectives();
1160            }
1161            else
1162            {
1163                return dependency.getIncludeDirectives( category );
1164            }
1165        }
1166        
1167        private void expandDefaultResource( 
1168          final List visited, final List stack, final Scope scope, final DefaultResource resource )
1169        {
1170            if( visited.contains( resource ) )
1171            {
1172                return;
1173            }
1174            else
1175            {
1176                visited.add( resource );
1177                boolean flag = !scope.equals( Scope.BUILD );
1178                DefaultResource[] providers = resource.getAggregatedDefaultProviders( scope, false, flag );
1179                for( int i=0; i<providers.length; i++ )
1180                {
1181                    DefaultResource provider = providers[i];
1182                    expandDefaultResource( visited, stack, scope, provider );
1183                }
1184                stack.add( resource );
1185            }
1186        }
1187        
1188        private String getIncludeReference( final IncludeDirective directive )
1189        {
1190            if( null == m_parent )
1191            {
1192                return directive.getValue();
1193            }
1194            else
1195            {
1196                if( IncludeDirective.REF.equals( directive.getMode() ) )
1197                {
1198                    return directive.getValue();
1199                }
1200                else
1201                {
1202                    String path = m_parent.getResourcePath();
1203                    if( "".equals( path ) )
1204                    {
1205                        return directive.getValue();
1206                    }
1207                    else
1208                    {
1209                        String key = directive.getValue();
1210                        return path + "/" + key;
1211                    }
1212                }
1213            }
1214        }
1215        
1216        //----------------------------------------------------------------------------
1217        // consumer concerns
1218        //----------------------------------------------------------------------------
1219        
1220        boolean isaConsumer( final DefaultResource resource )
1221        {
1222            DefaultResource[] resources = getAggregatedDefaultProviders( Scope.TEST, false, false );
1223            for( int i=0; i<resources.length; i++ )
1224            {
1225                DefaultResource provider = resources[i];
1226                if( resource.equals( provider ) )
1227                {
1228                    return true;
1229                }
1230            }
1231            return false;
1232        }
1233        
1234        DefaultResource[] getDefaultConsumers( final boolean expand, final boolean sort )
1235        {
1236            DefaultResource[] consumers = getDefaultConsumers( expand );
1237            if( sort )
1238            {
1239                return sortDefaultResources( consumers, Scope.TEST );
1240            }
1241            else
1242            {
1243                return consumers;
1244            }
1245        }
1246        
1247        DefaultResource[] getDefaultConsumers( final boolean expand )
1248        {
1249            if( !expand )
1250            {
1251                ArrayList list = new ArrayList();
1252                DefaultResource[] resources = m_library.selectDefaultResources( "**/*" );
1253                for( int i=0; i<resources.length; i++ )
1254                {
1255                    DefaultResource resource = resources[i];
1256                    if( !list.contains( resource ) && resource.isaConsumer( this ) )
1257                    {
1258                        list.add( resource );
1259                    }
1260                }
1261                return (DefaultResource[]) list.toArray( new DefaultResource[0] );
1262            }
1263            else
1264            {
1265                ArrayList visited = new ArrayList();
1266                ArrayList stack = new ArrayList();
1267                DefaultResource[] consumers = getDefaultConsumers( false );
1268                for( int i=0; i<consumers.length; i++ )
1269                {
1270                    DefaultResource consumer = consumers[i];
1271                    processConsumer( visited, stack, consumer );
1272                }
1273                return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1274            }
1275        }
1276        
1277        void processConsumer( final List visited, final List stack, final DefaultResource consumer )
1278        {
1279            if( visited.contains( consumer ) )
1280            {
1281                return;
1282            }
1283            visited.add( consumer );
1284            stack.add( consumer );
1285            DefaultResource[] resources = consumer.getDefaultConsumers( false, false );
1286            for( int i=0; i<resources.length; i++ )
1287            {
1288                DefaultResource resource = resources[i];
1289                processConsumer( visited, stack, resource );
1290            }
1291        }
1292        
1293        //----------------------------------------------------------------------------
1294        // classpath stuff
1295        //----------------------------------------------------------------------------
1296        
1297       /**
1298        * Construct an array of resources based on the RUNTIME scoped dependencies
1299        * associated with the supplied category.  The implementation builds a list
1300        * of all preceeding categories as a basis for filtering the returned list ensuring
1301        * no duplicate references are returned.
1302        * @param category the runtime classloader category
1303        * @return the array of resources the define a classloader for the category
1304        */
1305        private DefaultResource[] getClasspathDefaultProviders( final Category category )
1306        {
1307            ArrayList list = new ArrayList();
1308            for( int i=0; i<category.getValue(); i++ )
1309            {   
1310                Category c = Category.parse( i );
1311                DefaultResource[] collection = 
1312                  getDefaultProviders( Scope.RUNTIME, true, c );
1313                for( int j=0; j<collection.length; j++ )
1314                {
1315                    list.add( collection[j] );
1316                }
1317            }
1318            DefaultResource[] resources = 
1319              getDefaultProviders( Scope.RUNTIME, true, category );
1320            ArrayList stack = new ArrayList();
1321            for( int i=0; i<resources.length; i++ )
1322            {
1323                DefaultResource resource = resources[i];
1324                if( resource.isa( "jar" ) && !list.contains( resource ) )
1325                {
1326                    stack.add( resource );
1327                }
1328            }
1329            
1330            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1331        }
1332        
1333        //----------------------------------------------------------------------------
1334        // sorting relative to dependencies
1335        //----------------------------------------------------------------------------
1336        
1337        DefaultResource[] sortDefaultResources( final DefaultResource[] resources )
1338        {
1339            return sortDefaultResources( resources, Scope.TEST );
1340        }
1341        
1342        DefaultResource[] sortDefaultResources( final DefaultResource[] resources, final Scope scope )
1343        {
1344            ArrayList visited = new ArrayList();
1345            ArrayList stack = new ArrayList();
1346            for( int i=0; i<resources.length; i++ )
1347            {
1348                DefaultResource resource = resources[i];
1349                resource.sortDefaultResource( visited, stack, scope, resources );
1350            }
1351            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1352        }
1353        
1354        void sortDefaultResource( 
1355          final List visited, final List stack, final Scope scope, final DefaultResource[] resources )
1356        {
1357            if( visited.contains( this ) )
1358            {
1359                return;
1360            }
1361            else
1362            {
1363                visited.add( this );
1364                DefaultResource[] providers = 
1365                  getAggregatedDefaultProviders( scope, false, false );
1366                for( int i=0; i<providers.length; i++ )
1367                {
1368                    DefaultResource provider = providers[i];
1369                    if( isaMember( resources, provider ) )
1370                    {
1371                        provider.sortDefaultResource( visited, stack, scope, resources );
1372                    }
1373                }
1374                if( !stack.contains( this ) )
1375                {
1376                    stack.add( this );
1377                }
1378            }
1379        }
1380        
1381        boolean isaMember( final DefaultResource[] resources, final DefaultResource resource )
1382        {
1383            for( int i=0; i<resources.length; i++ )
1384            {
1385                DefaultResource r = resources[i];
1386                if( resource == r )
1387                {
1388                    return true;
1389                }
1390            }
1391            return false;
1392        }
1393        
1394        //----------------------------------------------------------------------------
1395        // version utilities
1396        //----------------------------------------------------------------------------
1397    
1398        private String getStandardVersion()
1399        {
1400            String value = getBuildSignature();
1401            if( value.equals( SNAPSHOT ) )
1402            {
1403                return value;
1404            }
1405            else
1406            {
1407                Version decimal = getDecimalVersion();
1408                if( null != decimal )
1409                {
1410                    boolean flag = getBooleanProperty( "project.version-postfix.enabled", true );
1411                    if( flag )
1412                    {
1413                        return decimal.toString() + "-" + value;
1414                    }
1415                    else
1416                    {
1417                        return decimal.toString();
1418                    }
1419                }
1420                else
1421                { 
1422                    return value;
1423                }
1424            }
1425        }
1426        
1427        private String getBuildSignature()
1428        {
1429            String system = System.getProperty( "build.signature", null );
1430            String value = getProperty( "build.signature", system );
1431            if( null == value )
1432            {
1433                return SNAPSHOT;
1434            }
1435            else if( value.equals( "project.timestamp" ) )
1436            {
1437                return TIMESTAMP;
1438            }
1439            else
1440            {
1441                return value;
1442            }
1443        }
1444        
1445        private int getMajorVersion()
1446        {
1447            return getIntegerProperty( "project.major.version", 0 );
1448        }
1449        
1450        private int getMinorVersion()
1451        {
1452            return getIntegerProperty( "project.minor.version", 0 );
1453        }
1454        
1455        private int getMicroVersion()
1456        {
1457            return getIntegerProperty( "project.micro.version", 0 );
1458        }
1459        
1460       /**
1461        * Return the UTC YYMMDD.HHMMSSS signature of a date.
1462        * @return the UTC date-stamp signature
1463        */
1464        public static String getTimestamp()
1465        {
1466            return getTimestamp( new Date() );
1467        }
1468        
1469       /**
1470        * Return the UTC YYMMDD.HHMMSSS signature of a date.
1471        * @param date the date
1472        * @return the UTC date-stamp signature
1473        */
1474        public static String getTimestamp( final Date date )
1475        {
1476            final SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
1477            sdf.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
1478            return sdf.format( date );
1479        }
1480        
1481        //----------------------------------------------------------------------------
1482        // other utilities
1483        //----------------------------------------------------------------------------
1484    
1485        private File getAnchor()
1486        {
1487            if( null != m_parent )
1488            {
1489                File anchor = m_parent.getBaseDir();
1490                if( null != anchor )
1491                {
1492                    return anchor;
1493                }
1494            }
1495            return m_library.getRootDirectory();
1496        }
1497        
1498        File getCanonicalFile( final File file )
1499        {
1500            try
1501            {
1502                return file.getCanonicalFile();
1503            }
1504            catch( IOException e )
1505            {
1506                final String error = 
1507                  "internal error while attempting to convert the file ["
1508                  + file
1509                  + "] to its canonical representation.";
1510                throw new RuntimeException( error, e );
1511            }
1512        }
1513        
1514        private String getGroupName()
1515        {
1516            if( m_parent.isRoot() )
1517            {
1518                return null;
1519            }
1520            else
1521            {
1522                return m_parent.getResourcePath();
1523            }
1524        }
1525    }